home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / css.js < prev    next >
Text File  |  2010-01-15  |  66KB  |  2,021 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule;
  11. const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
  12. const nsISelectionDisplay = Ci.nsISelectionDisplay;
  13. const nsISelectionController = Ci.nsISelectionController;
  14.  
  15. // See: http://mxr.mozilla.org/mozilla1.9.2/source/content/events/public/nsIEventStateManager.h#153
  16. const STATE_ACTIVE  = 0x01;
  17. const STATE_FOCUS   = 0x02;
  18. const STATE_HOVER   = 0x04;
  19.  
  20. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  21.  
  22. var domUtils = null;
  23.  
  24. var CSSDomplateBase = {
  25.     isEditable: function(rule)
  26.     {
  27.         return !rule.isSystemSheet;
  28.     },
  29.     isSelectorEditable: function(rule)
  30.     {
  31.         return rule.isSelectorEditable && this.isEditable(rule);
  32.     }
  33. };
  34.  
  35. var CSSPropTag = domplate(CSSDomplateBase, {
  36.     tag: DIV({class: "cssProp focusRow", $disabledStyle: "$prop.disabled",
  37.           $editGroup: "$rule|isEditable",
  38.           $cssOverridden: "$prop.overridden", role : "option"},
  39.         SPAN({class: "cssPropName", $editable: "$rule|isEditable"}, "$prop.name"),
  40.         SPAN({class: "cssColon"}, ":"),
  41.         SPAN({class: "cssPropValue", $editable: "$rule|isEditable"}, "$prop.value$prop.important"),
  42.         SPAN({class: "cssSemi"}, ";")
  43.     )
  44. });
  45.  
  46. var CSSRuleTag =
  47.     TAG("$rule.tag", {rule: "$rule"});
  48.  
  49. var CSSImportRuleTag = domplate({
  50.     tag: DIV({class: "cssRule insertInto focusRow importRule", _repObject: "$rule.rule"},
  51.         "@import "",
  52.         A({class: "objectLink", _repObject: "$rule.rule.styleSheet"}, "$rule.rule.href"),
  53.         "";"
  54.     )
  55. });
  56.  
  57. var CSSStyleRuleTag = domplate(CSSDomplateBase, {
  58.     tag: DIV({class: "cssRule insertInto",
  59.             $cssEditableRule: "$rule|isEditable",
  60.             $editGroup: "$rule|isSelectorEditable",
  61.             _repObject: "$rule.rule",
  62.             "ruleId": "$rule.id", role : 'presentation'},
  63.         DIV({class: "cssHead focusRow", role : 'listitem'},
  64.             SPAN({class: "cssSelector", $editable: "$rule|isSelectorEditable"}, "$rule.selector"), " {"
  65.         ),
  66.         DIV({role : 'group'},
  67.             DIV({class : "cssPropertyListBox", role : 'listbox'},
  68.                 FOR("prop", "$rule.props",
  69.                     TAG(CSSPropTag.tag, {rule: "$rule", prop: "$prop"})
  70.                 )
  71.             )
  72.         ),
  73.         DIV({class: "editable insertBefore", role:"presentation"}, "}")
  74.     )
  75. });
  76.  
  77. const reSplitCSS =  /(url\("?[^"\)]+?"?\))|(rgb\(.*?\))|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,2})?)|([^,\s]+)|"(.*?)"/;
  78.  
  79. const reURL = /url\("?([^"\)]+)?"?\)/;
  80.  
  81. const reRepeat = /no-repeat|repeat-x|repeat-y|repeat/;
  82.  
  83. const sothinkInstalled = !!$("swfcatcherKey_sidebar");
  84. const styleGroups =
  85. {
  86.     text: [
  87.         "font-family",
  88.         "font-size",
  89.         "font-weight",
  90.         "font-style",
  91.         "color",
  92.         "text-transform",
  93.         "text-decoration",
  94.         "letter-spacing",
  95.         "word-spacing",
  96.         "line-height",
  97.         "text-align",
  98.         "vertical-align",
  99.         "direction",
  100.         "column-count",
  101.         "column-gap",
  102.         "column-width"
  103.     ],
  104.  
  105.     background: [
  106.         "background-color",
  107.         "background-image",
  108.         "background-repeat",
  109.         "background-position",
  110.         "background-attachment",
  111.         "opacity"
  112.     ],
  113.  
  114.     box: [
  115.         "width",
  116.         "height",
  117.         "top",
  118.         "right",
  119.         "bottom",
  120.         "left",
  121.         "margin-top",
  122.         "margin-right",
  123.         "margin-bottom",
  124.         "margin-left",
  125.         "padding-top",
  126.         "padding-right",
  127.         "padding-bottom",
  128.         "padding-left",
  129.         "border-top-width",
  130.         "border-right-width",
  131.         "border-bottom-width",
  132.         "border-left-width",
  133.         "border-top-color",
  134.         "border-right-color",
  135.         "border-bottom-color",
  136.         "border-left-color",
  137.         "border-top-style",
  138.         "border-right-style",
  139.         "border-bottom-style",
  140.         "border-left-style",
  141.         "-moz-border-top-radius",
  142.         "-moz-border-right-radius",
  143.         "-moz-border-bottom-radius",
  144.         "-moz-border-left-radius",
  145.         "outline-top-width",
  146.         "outline-right-width",
  147.         "outline-bottom-width",
  148.         "outline-left-width",
  149.         "outline-top-color",
  150.         "outline-right-color",
  151.         "outline-bottom-color",
  152.         "outline-left-color",
  153.         "outline-top-style",
  154.         "outline-right-style",
  155.         "outline-bottom-style",
  156.         "outline-left-style"
  157.     ],
  158.  
  159.     layout: [
  160.         "position",
  161.         "display",
  162.         "visibility",
  163.         "z-index",
  164.         "overflow-x",  // http://www.w3.org/TR/2002/WD-css3-box-20021024/#overflow
  165.         "overflow-y",
  166.         "overflow-clip",
  167.         "white-space",
  168.         "clip",
  169.         "float",
  170.         "clear",
  171.         "-moz-box-sizing"
  172.     ],
  173.  
  174.     other: [
  175.         "cursor",
  176.         "list-style-image",
  177.         "list-style-position",
  178.         "list-style-type",
  179.         "marker-offset",
  180.         "user-focus",
  181.         "user-select",
  182.         "user-modify",
  183.         "user-input"
  184.     ]
  185. };
  186.  
  187. Firebug.CSSModule = extend(Firebug.Module,
  188. {
  189.     freeEdit: function(styleSheet, value)
  190.     {
  191.         if (!styleSheet.editStyleSheet)
  192.         {
  193.             var ownerNode = getStyleSheetOwnerNode(styleSheet);
  194.             styleSheet.disabled = true;
  195.  
  196.             var url = CCSV("@mozilla.org/network/standard-url;1", Components.interfaces.nsIURL);
  197.             url.spec = styleSheet.href;
  198.  
  199.             var editStyleSheet = ownerNode.ownerDocument.createElementNS(
  200.                 "http://www.w3.org/1999/xhtml",
  201.                 "style");
  202.             unwrapObject(editStyleSheet).firebugIgnore = true;
  203.             editStyleSheet.setAttribute("type", "text/css");
  204.             editStyleSheet.setAttributeNS(
  205.                 "http://www.w3.org/XML/1998/namespace",
  206.                 "base",
  207.                 url.directory);
  208.             if (ownerNode.hasAttribute("media"))
  209.             {
  210.               editStyleSheet.setAttribute("media", ownerNode.getAttribute("media"));
  211.             }
  212.  
  213.             // Insert the edited stylesheet directly after the old one to ensure the styles
  214.             // cascade properly.
  215.             ownerNode.parentNode.insertBefore(editStyleSheet, ownerNode.nextSibling);
  216.  
  217.             styleSheet.editStyleSheet = editStyleSheet;
  218.         }
  219.  
  220.         styleSheet.editStyleSheet.innerHTML = value;
  221.         dispatch(this.fbListener, "onCSSFreeEdit", [styleSheet, value]);
  222.     },
  223.  
  224.     insertRule: function(styleSheet, cssText, ruleIndex)
  225.     {
  226.         var insertIndex = styleSheet.insertRule(cssText, ruleIndex);
  227.  
  228.         dispatch(this.fbListeners, "onCSSInsertRule", [styleSheet, cssText, ruleIndex]);
  229.  
  230.         return insertIndex;
  231.     },
  232.  
  233.     deleteRule: function(styleSheet, ruleIndex)
  234.     {
  235.         dispatch(this.fbListeners, "onCSSDeleteRule", [styleSheet, ruleIndex]);
  236.  
  237.         styleSheet.deleteRule(ruleIndex);
  238.     },
  239.  
  240.     setProperty: function(rule, propName, propValue, propPriority)
  241.     {
  242.         var style = rule.style || rule;
  243.  
  244.         // Record the original CSS text for the inline case so we can reconstruct at a later
  245.         // point for diffing purposes
  246.         var baseText = style.cssText;
  247.  
  248.         var prevValue = style.getPropertyValue(propName);
  249.         var prevPriority = style.getPropertyPriority(propName);
  250.  
  251.         // XXXjoe Gecko bug workaround: Just changing priority doesn't have any effect
  252.         // unless we remove the property first
  253.         style.removeProperty(propName);
  254.  
  255.         style.setProperty(propName, propValue, propPriority);
  256.  
  257.         if (propName) {
  258.             dispatch(this.fbListeners, "onCSSSetProperty", [style, propName, propValue, propPriority, prevValue, prevPriority, rule, baseText]);
  259.         }
  260.     },
  261.  
  262.     removeProperty: function(rule, propName, parent)
  263.     {
  264.         var style = rule.style || rule;
  265.  
  266.         // Record the original CSS text for the inline case so we can reconstruct at a later
  267.         // point for diffing purposes
  268.         var baseText = style.cssText;
  269.  
  270.         var prevValue = style.getPropertyValue(propName);
  271.         var prevPriority = style.getPropertyPriority(propName);
  272.  
  273.         style.removeProperty(propName);
  274.  
  275.         if (propName) {
  276.             dispatch(this.fbListeners, "onCSSRemoveProperty", [style, propName, prevValue, prevPriority, rule, baseText]);
  277.         }
  278.     },
  279.  
  280.     cleanupSheets: function(doc, context)
  281.     {
  282.         // Due to the manner in which the layout engine handles multiple
  283.         // references to the same sheet we need to kick it a little bit.
  284.         // The injecting a simple stylesheet then removing it will force
  285.         // Firefox to regenerate it's CSS hierarchy.
  286.         //
  287.         // WARN: This behavior was determined anecdotally.
  288.         // See http://code.google.com/p/fbug/issues/detail?id=2440
  289.         var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
  290.         style.setAttribute("charset","utf-8");
  291.         unwrapObject(style).firebugIgnore = true;
  292.         style.setAttribute("type", "text/css");
  293.         style.innerHTML = "#fbIgnoreStyleDO_NOT_USE {}";
  294.         addStyleSheet(doc, style);
  295.         style.parentNode.removeChild(style);
  296.  
  297.         // https://bugzilla.mozilla.org/show_bug.cgi?id=500365
  298.         // This voodoo touches each style sheet to force some Firefox internal change to allow edits.
  299.         var styleSheets = getAllStyleSheets(context);
  300.         for(var i = 0; i < styleSheets.length; i++)
  301.         {
  302.             try
  303.             {
  304.                 var rules = styleSheets[i].cssRules;
  305.                 if (rules.length > 0)
  306.                     var touch = rules[0];
  307.             }
  308.             catch(e)
  309.             {
  310.             }
  311.         }
  312.     },
  313.     cleanupSheetHandler: function(event, context)
  314.     {
  315.         var target = event.target,
  316.             tagName = (target.tagName || "").toLowerCase();
  317.         if (tagName == "link")
  318.         {
  319.             this.cleanupSheets(target.ownerDocument, context);
  320.         }
  321.     },
  322.     watchWindow: function(context, win)
  323.     {
  324.         var cleanupSheets = bind(this.cleanupSheets, this),
  325.             cleanupSheetHandler = bind(this.cleanupSheetHandler, this, context),
  326.             doc = win.document;
  327.  
  328.         doc.addEventListener("DOMAttrModified", cleanupSheetHandler, false);
  329.         doc.addEventListener("DOMNodeInserted", cleanupSheetHandler, false);
  330.     },
  331.     loadedContext: function(context)
  332.     {
  333.         var self = this;
  334.         iterateWindows(context.browser.contentWindow, function(subwin)
  335.         {
  336.             self.cleanupSheets(subwin.document, context);
  337.         });
  338.     }
  339. });
  340.  
  341. // ************************************************************************************************
  342.  
  343. Firebug.CSSStyleSheetPanel = function() {}
  344.  
  345. Firebug.CSSStyleSheetPanel.prototype = extend(Firebug.SourceBoxPanel,
  346. {
  347.     template: domplate(
  348.     {
  349.         tag:
  350.             DIV({class: "cssSheet insertInto a11yCSSView"},
  351.                 FOR("rule", "$rules",
  352.                     CSSRuleTag
  353.                 ),
  354.                 DIV({class: "cssSheet editable insertBefore"}, "")
  355.                 )
  356.     }),
  357.  
  358.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  359.  
  360.     refresh: function()
  361.     {
  362.         if (this.location)
  363.             this.updateLocation(this.location);
  364.         else if (this.selection)
  365.             this.updateSelection(this.selection);
  366.     },
  367.  
  368.     toggleEditing: function()
  369.     {
  370.         if (!this.stylesheetEditor)
  371.             this.stylesheetEditor = new StyleSheetEditor(this.document);
  372.  
  373.         if (this.editing)
  374.             Firebug.Editor.stopEditing();
  375.         else
  376.         {
  377.             if (!this.location)
  378.                 return;
  379.  
  380.             var styleSheet = this.location.editStyleSheet
  381.                 ? this.location.editStyleSheet.sheet
  382.                 : this.location;
  383.  
  384.             var css = getStyleSheetCSS(styleSheet, this.context);
  385.             //var topmost = getTopmostRuleLine(this.panelNode);
  386.  
  387.             this.stylesheetEditor.styleSheet = this.location;
  388.             Firebug.Editor.startEditing(this.panelNode, css, this.stylesheetEditor);
  389.             //this.stylesheetEditor.scrollToLine(topmost.line, topmost.offset);
  390.         }
  391.     },
  392.  
  393.     getStylesheetURL: function(rule)
  394.     {
  395.         if (this.location.href)
  396.             return this.location.href;
  397.         else
  398.             return this.context.window.location.href;
  399.     },
  400.  
  401.     getRuleByLine: function(styleSheet, line)
  402.     {
  403.         if (!domUtils)
  404.             return null;
  405.  
  406.         var cssRules = styleSheet.cssRules;
  407.         for (var i = 0; i < cssRules.length; ++i)
  408.         {
  409.             var rule = cssRules[i];
  410.             if (rule instanceof CSSStyleRule)
  411.             {
  412.                 var ruleLine = domUtils.getRuleLine(rule);
  413.                 if (ruleLine >= line)
  414.                     return rule;
  415.             }
  416.         }
  417.     },
  418.  
  419.     highlightRule: function(rule)
  420.     {
  421.         var ruleElement = Firebug.getElementByRepObject(this.panelNode.firstChild, rule);
  422.         if (ruleElement)
  423.         {
  424.             scrollIntoCenterView(ruleElement, this.panelNode);
  425.             setClassTimed(ruleElement, "jumpHighlight", this.context);
  426.         }
  427.     },
  428.  
  429.     getStyleSheetRules: function(context, styleSheet)
  430.     {
  431.         var isSystemSheet = isSystemStyleSheet(styleSheet);
  432.  
  433.         function appendRules(cssRules)
  434.         {
  435.             for (var i = 0; i < cssRules.length; ++i)
  436.             {
  437.                 var rule = cssRules[i];
  438.                 if (rule instanceof CSSStyleRule)
  439.                 {
  440.                     var props = this.getRuleProperties(context, rule);
  441.                     var line = domUtils.getRuleLine(rule);
  442.                     var ruleId = rule.selectorText+"/"+line;
  443.                     rules.push({tag: CSSStyleRuleTag.tag, rule: rule, id: ruleId,
  444.                                 selector: rule.selectorText, props: props,
  445.                                 isSystemSheet: isSystemSheet,
  446.                                 isSelectorEditable: true});
  447.                 }
  448.                 else if (rule instanceof CSSImportRule)
  449.                     rules.push({tag: CSSImportRuleTag.tag, rule: rule});
  450.                 else if (rule instanceof CSSMediaRule)
  451.                     appendRules.apply(this, [rule.cssRules]);
  452.                 else
  453.                 {
  454.                 }
  455.             }
  456.         }
  457.  
  458.         var rules = [];
  459.         appendRules.apply(this, [styleSheet.cssRules]);
  460.         return rules;
  461.     },
  462.  
  463.     parseCSSProps: function(style, inheritMode)
  464.     {
  465.         var props = [];
  466.  
  467.         if (Firebug.expandShorthandProps)
  468.         {
  469.             var count = style.length-1,
  470.                 index = style.length;
  471.             while (index--)
  472.             {
  473.                 var propName = style.item(count - index);
  474.                 this.addProperty(propName, style.getPropertyValue(propName), !!style.getPropertyPriority(propName), false, inheritMode, props);
  475.             }
  476.         }
  477.         else
  478.         {
  479.             var lines = style.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g);
  480.             var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/;
  481.             var line,i=0;
  482.             while(line=lines[i++]){
  483.                 m = propRE.exec(line);
  484.                 if(!m)
  485.                     continue;
  486.                 //var name = m[1], value = m[2], important = !!m[3];
  487.                 if (m[2])
  488.                     this.addProperty(m[1], m[2], !!m[3], false, inheritMode, props);
  489.             };
  490.         }
  491.  
  492.         return props;
  493.     },
  494.     getRuleProperties: function(context, rule, inheritMode)
  495.     {
  496.         var props = this.parseCSSProps(rule.style, inheritMode);
  497.  
  498.         line = domUtils.getRuleLine(rule);
  499.         var ruleId = rule.selectorText+"/"+line;
  500.         this.addOldProperties(context, ruleId, inheritMode, props);
  501.         sortProperties(props);
  502.  
  503.         return props;
  504.     },
  505.  
  506.     addOldProperties: function(context, ruleId, inheritMode, props)
  507.     {
  508.         if (context.selectorMap && context.selectorMap.hasOwnProperty(ruleId) )
  509.         {
  510.             var moreProps = context.selectorMap[ruleId];
  511.             for (var i = 0; i < moreProps.length; ++i)
  512.             {
  513.                 var prop = moreProps[i];
  514.                 this.addProperty(prop.name, prop.value, prop.important, true, inheritMode, props);
  515.             }
  516.         }
  517.     },
  518.  
  519.     addProperty: function(name, value, important, disabled, inheritMode, props)
  520.     {
  521.         if (inheritMode && !inheritedStyleNames[name])
  522.             return;
  523.  
  524.         name = this.translateName(name, value);
  525.         if (name)
  526.         {
  527.             value = stripUnits(rgbToHex(value));
  528.             important = important ? " !important" : "";
  529.  
  530.             var prop = {name: name, value: value, important: important, disabled: disabled};
  531.             props.push(prop);
  532.         }
  533.     },
  534.  
  535.     translateName: function(name, value)
  536.     {
  537.         // Don't show these proprietary Mozilla properties
  538.         if ((value == "-moz-initial"
  539.             && (name == "-moz-background-clip" || name == "-moz-background-origin"
  540.                 || name == "-moz-background-inline-policy"))
  541.         || (value == "physical"
  542.             && (name == "margin-left-ltr-source" || name == "margin-left-rtl-source"
  543.                 || name == "margin-right-ltr-source" || name == "margin-right-rtl-source"))
  544.         || (value == "physical"
  545.             && (name == "padding-left-ltr-source" || name == "padding-left-rtl-source"
  546.                 || name == "padding-right-ltr-source" || name == "padding-right-rtl-source")))
  547.             return null;
  548.  
  549.         // Translate these back to the form the user probably expects
  550.         if (name == "margin-left-value")
  551.             return "margin-left";
  552.         else if (name == "margin-right-value")
  553.             return "margin-right";
  554.         else if (name == "margin-top-value")
  555.             return "margin-top";
  556.         else if (name == "margin-bottom-value")
  557.             return "margin-bottom";
  558.         else if (name == "padding-left-value")
  559.             return "padding-left";
  560.         else if (name == "padding-right-value")
  561.             return "padding-right";
  562.         else if (name == "padding-top-value")
  563.             return "padding-top";
  564.         else if (name == "padding-bottom-value")
  565.             return "padding-bottom";
  566.         // XXXjoe What about border!
  567.         else
  568.             return name;
  569.     },
  570.  
  571.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  572.  
  573.     editElementStyle: function()
  574.     {
  575.         var rulesBox = this.panelNode.getElementsByClassName("cssElementRuleContainer")[0];
  576.         var styleRuleBox = rulesBox && Firebug.getElementByRepObject(rulesBox, this.selection);
  577.         if (!styleRuleBox)
  578.         {
  579.             var rule = {rule: this.selection, inherited: false, selector: "element.style", props: []};
  580.             if (!rulesBox)
  581.             {
  582.                 // The element did not have any displayed styles. We need to create the whole tree and remove
  583.                 // the no styles message
  584.                 styleRuleBox = this.template.cascadedTag.replace({
  585.                     rules: [rule], inherited: [], inheritLabel: $STR("InheritedFrom")
  586.                 }, this.panelNode);
  587.  
  588.                 styleRuleBox = styleRuleBox.getElementsByClassName("cssElementRuleContainer")[0];
  589.             }
  590.             else
  591.                 styleRuleBox = this.template.ruleTag.insertBefore({rule: rule}, rulesBox);
  592.  
  593.             styleRuleBox = styleRuleBox.getElementsByClassName("insertInto")[0];
  594.         }
  595.  
  596.         Firebug.Editor.insertRowForObject(styleRuleBox);
  597.     },
  598.  
  599.     insertPropertyRow: function(row)
  600.     {
  601.         Firebug.Editor.insertRowForObject(row);
  602.     },
  603.  
  604.     insertRule: function(row)
  605.     {
  606.         var location = getAncestorByClass(row, "cssRule");
  607.         if (!location)
  608.         {
  609.             location = getChildByClass(this.panelNode, "cssSheet");
  610.             Firebug.Editor.insertRowForObject(location);
  611.         }
  612.         else
  613.         {
  614.             Firebug.Editor.insertRow(location, "before");
  615.         }
  616.     },
  617.  
  618.     editPropertyRow: function(row)
  619.     {
  620.         var propValueBox = getChildByClass(row, "cssPropValue");
  621.         Firebug.Editor.startEditing(propValueBox);
  622.     },
  623.  
  624.     deletePropertyRow: function(row)
  625.     {
  626.         var rule = Firebug.getRepObject(row);
  627.         var propName = getChildByClass(row, "cssPropName").textContent;
  628.         Firebug.CSSModule.removeProperty(rule, propName);
  629.  
  630.         // Remove the property from the selector map, if it was disabled
  631.         var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
  632.         if ( this.context.selectorMap && this.context.selectorMap.hasOwnProperty(ruleId) )
  633.         {
  634.             var map = this.context.selectorMap[ruleId];
  635.             for (var i = 0; i < map.length; ++i)
  636.             {
  637.                 if (map[i].name == propName)
  638.                 {
  639.                     map.splice(i, 1);
  640.                     break;
  641.                 }
  642.             }
  643.         }
  644.         if (this.name == "stylesheet")
  645.             dispatch([Firebug.A11yModel], 'onInlineEditorClose', [this, row.firstChild, true]);
  646.         row.parentNode.removeChild(row);
  647.  
  648.         this.markChange(this.name == "stylesheet");
  649.     },
  650.  
  651.     disablePropertyRow: function(row)
  652.     {
  653.         toggleClass(row, "disabledStyle");
  654.  
  655.         var rule = Firebug.getRepObject(row);
  656.         var propName = getChildByClass(row, "cssPropName").textContent;
  657.  
  658.         if (!this.context.selectorMap)
  659.             this.context.selectorMap = {};
  660.  
  661.         // XXXjoe Generate unique key for elements too
  662.         var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
  663.         if (!(this.context.selectorMap.hasOwnProperty(ruleId)))
  664.             this.context.selectorMap[ruleId] = [];
  665.  
  666.         var map = this.context.selectorMap[ruleId];
  667.         var propValue = getChildByClass(row, "cssPropValue").textContent;
  668.         var parsedValue = parsePriority(propValue);
  669.         if (hasClass(row, "disabledStyle"))
  670.         {
  671.             Firebug.CSSModule.removeProperty(rule, propName);
  672.  
  673.             map.push({"name": propName, "value": parsedValue.value,
  674.                 "important": parsedValue.priority});
  675.         }
  676.         else
  677.         {
  678.             Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority);
  679.  
  680.             var index = findPropByName(map, propName);
  681.             map.splice(index, 1);
  682.         }
  683.  
  684.         this.markChange(this.name == "stylesheet");
  685.     },
  686.  
  687.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  688.  
  689.     onMouseDown: function(event)
  690.     {
  691.         // XXjoe Hack to only allow clicking on the checkbox
  692.         if (!isLeftClick(event) || event.clientX > 20)
  693.             return;
  694.  
  695.         if (hasClass(event.target, "textEditor"))
  696.             return;
  697.  
  698.         var row = getAncestorByClass(event.target, "cssProp");
  699.         if (row && hasClass(row, "editGroup"))
  700.         {
  701.             this.disablePropertyRow(row);
  702.             cancelEvent(event);
  703.         }
  704.     },
  705.  
  706.     onClick: function(event)
  707.     {
  708.         if (!isLeftClick(event) || event.clientX <= 20 || event.detail != 2)
  709.             return;
  710.  
  711.         var row = getAncestorByClass(event.target, "cssRule");
  712.         if (row && !getAncestorByClass(event.target, "cssPropName")
  713.             && !getAncestorByClass(event.target, "cssPropValue"))
  714.         {
  715.             this.insertPropertyRow(row);
  716.             cancelEvent(event);
  717.         }
  718.     },
  719.  
  720.  
  721.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  722.     // extends Panel
  723.  
  724.     name: "stylesheet",
  725.     parentPanel: null,
  726.     searchable: true,
  727.     dependents: ["css", "stylesheet", "dom", "domSide", "layout"],
  728.  
  729.     initialize: function()
  730.     {
  731.         if (!domUtils)
  732.         {
  733.             try {
  734.                 domUtils = CCSV("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
  735.             } catch (exc) {
  736.             }
  737.         }
  738.         this.onMouseDown = bind(this.onMouseDown, this);
  739.         this.onClick = bind(this.onClick, this);
  740.  
  741.         Firebug.SourceBoxPanel.initialize.apply(this, arguments);
  742.     },
  743.  
  744.     destroy: function(state)
  745.     {
  746.         state.scrollTop = this.panelNode.scrollTop ? this.panelNode.scrollTop : this.lastScrollTop;
  747.  
  748.         persistObjects(this, state);
  749.  
  750.         Firebug.Editor.stopEditing();
  751.         Firebug.Panel.destroy.apply(this, arguments);
  752.     },
  753.  
  754.     initializeNode: function(oldPanelNode)
  755.     {
  756.         this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
  757.         this.panelNode.addEventListener("click", this.onClick, false);
  758.         Firebug.SourceBoxPanel.initializeNode.apply(this, arguments);
  759.         dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'css']);
  760.     },
  761.  
  762.     destroyNode: function()
  763.     {
  764.         this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
  765.         this.panelNode.removeEventListener("click", this.onClick, false);
  766.         Firebug.SourceBoxPanel.destroyNode.apply(this, arguments);
  767.         dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'css']);
  768.     },
  769.  
  770.     show: function(state)
  771.     {
  772.         Firebug.Inspector.stopInspecting(true);
  773.  
  774.         this.showToolbarButtons("fbCSSButtons", true);
  775.  
  776.         if (this.context.loaded && !this.location) // wait for loadedContext to restore the panel
  777.         {
  778.             restoreObjects(this, state);
  779.  
  780.             if (!this.location)
  781.                 this.location = this.getDefaultLocation();
  782.  
  783.             if (state && state.scrollTop)
  784.                 this.panelNode.scrollTop = state.scrollTop;
  785.         }
  786.     },
  787.  
  788.     hide: function()
  789.     {
  790.         this.showToolbarButtons("fbCSSButtons", false);
  791.  
  792.         this.lastScrollTop = this.panelNode.scrollTop;
  793.     },
  794.  
  795.     supportsObject: function(object)
  796.     {
  797.         if (object instanceof CSSStyleSheet)
  798.             return 1;
  799.         else if (object instanceof CSSStyleRule)
  800.             return 2;
  801.         else if (object instanceof CSSStyleDeclaration)
  802.             return 2;
  803.         else if (object instanceof SourceLink && object.type == "css" && reCSS.test(object.href))
  804.             return 2;
  805.         else
  806.             return 0;
  807.     },
  808.  
  809.     updateLocation: function(styleSheet)
  810.     {
  811.         if (!styleSheet)
  812.             return;
  813.         if (styleSheet.editStyleSheet)
  814.             styleSheet = styleSheet.editStyleSheet.sheet;
  815.  
  816.         var rules = this.getStyleSheetRules(this.context, styleSheet);
  817.  
  818.         var result;
  819.         if (rules.length)
  820.             result = this.template.tag.replace({rules: rules}, this.panelNode);
  821.         else
  822.             result = FirebugReps.Warning.tag.replace({object: "EmptyStyleSheet"}, this.panelNode);
  823.  
  824.         this.showToolbarButtons("fbCSSButtons", !isSystemStyleSheet(this.location));
  825.  
  826.         dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, this.panelNode]);
  827.     },
  828.  
  829.     updateSelection: function(object)
  830.     {
  831.         this.selection = null;
  832.  
  833.         if (object instanceof CSSStyleDeclaration) {
  834.             object = object.parentRule;
  835.         }
  836.  
  837.         if (object instanceof CSSStyleRule)
  838.         {
  839.             this.navigate(object.parentStyleSheet);
  840.             this.highlightRule(object);
  841.         }
  842.         else if (object instanceof CSSStyleSheet)
  843.         {
  844.             this.navigate(object);
  845.         }
  846.         else if (object instanceof SourceLink)
  847.         {
  848.             try
  849.             {
  850.                 var sourceLink = object;
  851.  
  852.                 var sourceFile = getSourceFileByHref(sourceLink.href, this.context);
  853.                 if (sourceFile)
  854.                 {
  855.                     clearNode(this.panelNode);  // replace rendered stylesheets
  856.                     this.showSourceFile(sourceFile);
  857.  
  858.                     var lineNo = object.line;
  859.                     if (lineNo)
  860.                         this.scrollToLine(lineNo, this.jumpHighlightFactory(lineNo, this.context));
  861.                 }
  862.                 else // XXXjjb we should not be taking this path
  863.                 {
  864.                     var stylesheet = getStyleSheetByHref(sourceLink.href, this.context);
  865.                     if (stylesheet)
  866.                         this.navigate(stylesheet);
  867.                     else
  868.                     {
  869.                     }
  870.                 }
  871.             }
  872.             catch(exc) {
  873.             }
  874.         }
  875.     },
  876.  
  877.     updateOption: function(name, value)
  878.     {
  879.         if (name == "expandShorthandProps")
  880.             this.refresh();
  881.     },
  882.  
  883.     getLocationList: function()
  884.     {
  885.         var styleSheets = getAllStyleSheets(this.context);
  886.         return styleSheets;
  887.     },
  888.  
  889.     getOptionsMenuItems: function()
  890.     {
  891.         return [
  892.             {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps,
  893.                     command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") },
  894.             "-",
  895.             {label: "Refresh", command: bind(this.refresh, this) }
  896.         ];
  897.     },
  898.  
  899.     getContextMenuItems: function(style, target)
  900.     {
  901.         var items = [];
  902.  
  903.         if (this.infoTipType == "color")
  904.         {
  905.             items.push(
  906.                 {label: "CopyColor",
  907.                     command: bindFixed(copyToClipboard, FBL, this.infoTipObject) }
  908.             );
  909.         }
  910.         else if (this.infoTipType == "image")
  911.         {
  912.             items.push(
  913.                 {label: "CopyImageLocation",
  914.                     command: bindFixed(copyToClipboard, FBL, this.infoTipObject) },
  915.                 {label: "OpenImageInNewTab",
  916.                     command: bindFixed(openNewTab, FBL, this.infoTipObject) }
  917.             );
  918.         }
  919.  
  920.         if (this.selection instanceof Element)
  921.         {
  922.             items.push(
  923.                 "-",
  924.                 {label: "EditStyle",
  925.                     command: bindFixed(this.editElementStyle, this) }
  926.             );
  927.         }
  928.         else if (!isSystemStyleSheet(this.selection))
  929.         {
  930.             items.push(
  931.                     "-",
  932.                     {label: "NewRule",
  933.                         command: bindFixed(this.insertRule, this, target) }
  934.                 );
  935.         }
  936.  
  937.         var cssRule = getAncestorByClass(target, "cssRule")
  938.         if (cssRule && hasClass(cssRule, "cssEditableRule"))
  939.         {
  940.             items.push(
  941.                 "-",
  942.                 {label: "NewProp",
  943.                     command: bindFixed(this.insertPropertyRow, this, target) }
  944.             );
  945.  
  946.             var propRow = getAncestorByClass(target, "cssProp");
  947.             if (propRow)
  948.             {
  949.                 var propName = getChildByClass(propRow, "cssPropName").textContent;
  950.                 var isDisabled = hasClass(propRow, "disabledStyle");
  951.  
  952.                 items.push(
  953.                     {label: $STRF("EditProp", [propName]), nol10n: true,
  954.                         command: bindFixed(this.editPropertyRow, this, propRow) },
  955.                     {label: $STRF("DeleteProp", [propName]), nol10n: true,
  956.                         command: bindFixed(this.deletePropertyRow, this, propRow) },
  957.                     {label: $STRF("DisableProp", [propName]), nol10n: true,
  958.                         type: "checkbox", checked: isDisabled,
  959.                         command: bindFixed(this.disablePropertyRow, this, propRow) }
  960.                 );
  961.             }
  962.         }
  963.  
  964.         items.push(
  965.             "-",
  966.             {label: "Refresh", command: bind(this.refresh, this) }
  967.         );
  968.  
  969.         return items;
  970.     },
  971.  
  972.     browseObject: function(object)
  973.     {
  974.         if (this.infoTipType == "image")
  975.         {
  976.             openNewTab(this.infoTipObject);
  977.             return true;
  978.         }
  979.     },
  980.  
  981.     showInfoTip: function(infoTip, target, x, y)
  982.     {
  983.         var propValue = getAncestorByClass(target, "cssPropValue");
  984.         if (propValue)
  985.         {
  986.             var offset = getClientOffset(propValue);
  987.             var offsetX = x-offset.x;
  988.  
  989.             var text = propValue.textContent;
  990.             var charWidth = propValue.offsetWidth/text.length;
  991.             var charOffset = Math.floor(offsetX/charWidth);
  992.  
  993.             var cssValue = parseCSSValue(text, charOffset);
  994.             if (cssValue)
  995.             {
  996.                 if (cssValue.value == this.infoTipValue)
  997.                     return true;
  998.  
  999.                 this.infoTipValue = cssValue.value;
  1000.  
  1001.                 if (cssValue.type == "rgb" || (!cssValue.type && isColorKeyword(cssValue.value)))
  1002.                 {
  1003.                     this.infoTipType = "color";
  1004.                     this.infoTipObject = cssValue.value;
  1005.  
  1006.                     return Firebug.InfoTip.populateColorInfoTip(infoTip, cssValue.value);
  1007.                 }
  1008.                 else if (cssValue.type == "url")
  1009.                 {
  1010.                     var propNameNode = target.parentNode.getElementsByClassName("cssPropName").item(0);
  1011.                     if (propNameNode && isImageRule(propNameNode.textContent))
  1012.                     {
  1013.                         var rule = Firebug.getRepObject(target);
  1014.                         var baseURL = this.getStylesheetURL(rule);
  1015.                         var relURL = parseURLValue(cssValue.value);
  1016.                         var absURL = isDataURL(relURL) ? relURL:absoluteURL(relURL, baseURL);
  1017.                         var repeat = parseRepeatValue(text);
  1018.  
  1019.                         this.infoTipType = "image";
  1020.                         this.infoTipObject = absURL;
  1021.  
  1022.                         return Firebug.InfoTip.populateImageInfoTip(infoTip, absURL, repeat);
  1023.                     }
  1024.                 }
  1025.             }
  1026.         }
  1027.  
  1028.         delete this.infoTipType;
  1029.         delete this.infoTipValue;
  1030.         delete this.infoTipObject;
  1031.     },
  1032.  
  1033.     getEditor: function(target, value)
  1034.     {
  1035.         if (target == this.panelNode
  1036.             || hasClass(target, "cssSelector") || hasClass(target, "cssRule")
  1037.             || hasClass(target, "cssSheet"))
  1038.         {
  1039.             if (!this.ruleEditor)
  1040.                 this.ruleEditor = new CSSRuleEditor(this.document);
  1041.  
  1042.             return this.ruleEditor;
  1043.         }
  1044.         else
  1045.         {
  1046.             if (!this.editor)
  1047.                 this.editor = new CSSEditor(this.document);
  1048.  
  1049.             return this.editor;
  1050.         }
  1051.     },
  1052.  
  1053.     getDefaultLocation: function()
  1054.     {
  1055.         try
  1056.         {
  1057.             var styleSheets = this.context.window.document.styleSheets;
  1058.             if (styleSheets.length)
  1059.             {
  1060.                 var sheet = styleSheets[0];
  1061.                 return (Firebug.filterSystemURLs && isSystemURL(getURLForStyleSheet(sheet))) ? null : sheet;
  1062.             }
  1063.         }
  1064.         catch (exc)
  1065.         {
  1066.         }
  1067.     },
  1068.  
  1069.     getObjectDescription: function(styleSheet)
  1070.     {
  1071.         var url = getURLForStyleSheet(styleSheet);
  1072.         var instance = getInstanceForStyleSheet(styleSheet);
  1073.  
  1074.         var baseDescription = splitURLBase(url);
  1075.         if (instance) {
  1076.           baseDescription.name = baseDescription.name + " #" + (instance + 1);
  1077.         }
  1078.         return baseDescription;
  1079.     },
  1080.  
  1081.     search: function(text, reverse)
  1082.     {
  1083.         var curDoc = this.searchCurrentDoc(!Firebug.searchGlobal, text, reverse);
  1084.         if (!curDoc && Firebug.searchGlobal)
  1085.         {
  1086.             return this.searchOtherDocs(text, reverse);
  1087.         }
  1088.         return curDoc;
  1089.     },
  1090.  
  1091.     searchOtherDocs: function(text, reverse)
  1092.     {
  1093.         var scanRE = Firebug.Search.getTestingRegex(text);
  1094.         function scanDoc(styleSheet) {
  1095.             // we don't care about reverse here as we are just looking for existence,
  1096.             // if we do have a result we will handle the reverse logic on display
  1097.             for (var i = 0; i < styleSheet.cssRules.length; i++)
  1098.             {
  1099.                 if (scanRE.test(styleSheet.cssRules[i].cssText))
  1100.                 {
  1101.                     return true;
  1102.                 }
  1103.             }
  1104.         }
  1105.  
  1106.         if (this.navigateToNextDocument(scanDoc, reverse))
  1107.         {
  1108.             return this.searchCurrentDoc(true, text, reverse);
  1109.         }
  1110.     },
  1111.  
  1112.     searchCurrentDoc: function(wrapSearch, text, reverse)
  1113.     {
  1114.         if (!text)
  1115.         {
  1116.             delete this.currentSearch;
  1117.             return false;
  1118.         }
  1119.  
  1120.         var row;
  1121.         if (this.currentSearch && text == this.currentSearch.text)
  1122.         {
  1123.             row = this.currentSearch.findNext(wrapSearch, false, reverse, Firebug.Search.isCaseSensitive(text));
  1124.         }
  1125.         else
  1126.         {
  1127.             if (this.editing)
  1128.             {
  1129.                 this.currentSearch = new TextSearch(this.stylesheetEditor.box);
  1130.                 row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
  1131.  
  1132.                 if (row)
  1133.                 {
  1134.                     var sel = this.document.defaultView.getSelection();
  1135.                     sel.removeAllRanges();
  1136.                     sel.addRange(this.currentSearch.range);
  1137.                     scrollSelectionIntoView(this);
  1138.                     return true;
  1139.                 }
  1140.                 else
  1141.                     return false;
  1142.             }
  1143.             else
  1144.             {
  1145.                 function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; }
  1146.                 this.currentSearch = new TextSearch(this.panelNode, findRow);
  1147.                 row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
  1148.             }
  1149.         }
  1150.  
  1151.         if (row)
  1152.         {
  1153.             this.document.defaultView.getSelection().selectAllChildren(row);
  1154.             scrollIntoCenterView(row, this.panelNode);
  1155.             dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, row]);
  1156.             return true;
  1157.         }
  1158.         else
  1159.         {
  1160.             dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, null]);
  1161.             return false;
  1162.         }
  1163.     },
  1164.  
  1165.     getSearchOptionsMenuItems: function()
  1166.     {
  1167.         return [
  1168.             Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive"),
  1169.             Firebug.Search.searchOptionMenu("search.Multiple_Files", "searchGlobal")
  1170.         ];
  1171.     }
  1172. });
  1173.  
  1174. // ************************************************************************************************
  1175.  
  1176. function CSSElementPanel() {}
  1177.  
  1178. CSSElementPanel.prototype = extend(Firebug.CSSStyleSheetPanel.prototype,
  1179. {
  1180.     template: domplate(
  1181.     {
  1182.         cascadedTag:
  1183.             DIV({"class": "a11yCSSView",  role : 'presentation'},
  1184.                 DIV({role : 'list', 'aria-label' : $STR('aria.labels.style rules') },
  1185.                     FOR("rule", "$rules",
  1186.                         TAG("$ruleTag", {rule: "$rule"})
  1187.                     )
  1188.                 ),
  1189.                 DIV({role : "list", 'aria-label' :$STR('aria.labels.inherited style rules')},
  1190.                     FOR("section", "$inherited",
  1191.  
  1192.                         H1({class: "cssInheritHeader groupHeader focusRow", role : 'listitem' },
  1193.                             SPAN({class: "cssInheritLabel"}, "$inheritLabel"),
  1194.                             TAG(FirebugReps.Element.shortTag, {object: "$section.element"})
  1195.                         ),
  1196.                         DIV({role : 'group'},
  1197.                             FOR("rule", "$section.rules",
  1198.                                 TAG("$ruleTag", {rule: "$rule"})
  1199.                             )
  1200.                         )
  1201.                     )
  1202.                  )
  1203.             ),
  1204.  
  1205.         ruleTag:
  1206.           DIV({class: "cssElementRuleContainer"},
  1207.               TAG(CSSStyleRuleTag.tag, {rule: "$rule"}),
  1208.               TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"})
  1209.           )
  1210.     }),
  1211.  
  1212.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1213.  
  1214.     updateCascadeView: function(element)
  1215.     {
  1216.         dispatch([Firebug.A11yModel], 'onBeforeCSSRulesAdded', [this]);
  1217.         var rules = [], sections = [], usedProps = {};
  1218.         this.getInheritedRules(element, sections, usedProps);
  1219.         this.getElementRules(element, rules, usedProps);
  1220.  
  1221.         if (rules.length || sections.length)
  1222.         {
  1223.             var inheritLabel = $STR("InheritedFrom");
  1224.             var result = this.template.cascadedTag.replace({rules: rules, inherited: sections,
  1225.                 inheritLabel: inheritLabel}, this.panelNode);
  1226.             dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
  1227.         }
  1228.         else
  1229.         {
  1230.             var result = FirebugReps.Warning.tag.replace({object: "EmptyElementCSS"}, this.panelNode);
  1231.             dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
  1232.         }
  1233.     },
  1234.  
  1235.     getStylesheetURL: function(rule)
  1236.     {
  1237.         // if the parentStyleSheet.href is null, CSS std says its inline style
  1238.         if (rule && rule.parentStyleSheet.href)
  1239.             return rule.parentStyleSheet.href;
  1240.         else
  1241.             return this.selection.ownerDocument.location.href;
  1242.     },
  1243.  
  1244.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1245.  
  1246.     getInheritedRules: function(element, sections, usedProps)
  1247.     {
  1248.         var parent = element.parentNode;
  1249.         if (parent && parent.nodeType == 1)
  1250.         {
  1251.             this.getInheritedRules(parent, sections, usedProps);
  1252.  
  1253.             var rules = [];
  1254.             this.getElementRules(parent, rules, usedProps, true);
  1255.  
  1256.             if (rules.length)
  1257.                 sections.splice(0, 0, {element: parent, rules: rules});
  1258.         }
  1259.     },
  1260.  
  1261.     getElementRules: function(element, rules, usedProps, inheritMode)
  1262.     {
  1263.         var inspectedRules, displayedRules = {};
  1264.         try
  1265.         {
  1266.             inspectedRules = domUtils ? domUtils.getCSSStyleRules(element) : null;
  1267.         } catch (exc) {}
  1268.  
  1269.         if (inspectedRules)
  1270.         {
  1271.             for (var i = 0; i < inspectedRules.Count(); ++i)
  1272.             {
  1273.                 var rule = QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule);
  1274.  
  1275.                 var href = rule.parentStyleSheet.href;  // Null means inline
  1276.  
  1277.                 var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument);
  1278.  
  1279.                 var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet);
  1280.                 if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules
  1281.                     continue;
  1282.                 if (!href)
  1283.                     href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452
  1284.  
  1285.                 var props = this.getRuleProperties(this.context, rule, inheritMode);
  1286.                 if (inheritMode && !props.length)
  1287.                     continue;
  1288.  
  1289.                 var line = domUtils.getRuleLine(rule);
  1290.                 var ruleId = rule.selectorText+"/"+line;
  1291.                 var sourceLink = new SourceLink(href, line, "css", rule, instance);
  1292.  
  1293.                 this.markOverridenProps(props, usedProps, inheritMode);
  1294.  
  1295.                 rules.splice(0, 0, {rule: rule, id: ruleId,
  1296.                         selector: rule.selectorText, sourceLink: sourceLink,
  1297.                         props: props, inherited: inheritMode,
  1298.                         isSystemSheet: isSystemSheet});
  1299.             }
  1300.         }
  1301.  
  1302.         if (element.style)
  1303.             this.getStyleProperties(element, rules, usedProps, inheritMode);
  1304.  
  1305.     },
  1306.  
  1307.     markOverridenProps: function(props, usedProps, inheritMode)
  1308.     {
  1309.         for (var i = 0; i < props.length; ++i)
  1310.         {
  1311.             var prop = props[i];
  1312.             if ( usedProps.hasOwnProperty(prop.name) )
  1313.             {
  1314.                 var deadProps = usedProps[prop.name]; // all previous occurrences of this property
  1315.                 for (var j = 0; j < deadProps.length; ++j)
  1316.                 {
  1317.                     var deadProp = deadProps[j];
  1318.                     if (!deadProp.disabled && !deadProp.wasInherited && deadProp.important && !prop.important)
  1319.                         prop.overridden = true;  // new occurrence overridden
  1320.                     else if (!prop.disabled)
  1321.                         deadProp.overridden = true;  // previous occurrences overridden
  1322.                 }
  1323.             }
  1324.             else
  1325.                 usedProps[prop.name] = [];
  1326.  
  1327.             prop.wasInherited = inheritMode ? true : false;
  1328.             usedProps[prop.name].push(prop);  // all occurrences of a property seen so far, by name
  1329.         }
  1330.     },
  1331.  
  1332.     getStyleProperties: function(element, rules, usedProps, inheritMode)
  1333.     {
  1334.         var props = this.parseCSSProps(element.style, inheritMode);
  1335.         this.addOldProperties(this.context, getElementXPath(element), inheritMode, props);
  1336.  
  1337.         sortProperties(props);
  1338.         this.markOverridenProps(props, usedProps, inheritMode);
  1339.  
  1340.         if (props.length)
  1341.             rules.splice(0, 0,
  1342.                     {rule: element, id: getElementXPath(element),
  1343.                         selector: "element.style", props: props, inherited: inheritMode});
  1344.     },
  1345.  
  1346.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1347.     // extends Panel
  1348.  
  1349.     name: "css",
  1350.     parentPanel: "html",
  1351.     order: 0,
  1352.  
  1353.     initialize: function()
  1354.     {
  1355.         Firebug.CSSStyleSheetPanel.prototype.initialize.apply(this, arguments);
  1356.  
  1357.         this.onStateChange = bindFixed(this.contentStateCheck, this);
  1358.         this.onHoverChange = bindFixed(this.contentStateCheck, this, STATE_HOVER);
  1359.         this.onActiveChange = bindFixed(this.contentStateCheck, this, STATE_ACTIVE);
  1360.     },
  1361.  
  1362.     show: function(state)
  1363.     {
  1364.     },
  1365.  
  1366.     watchWindow: function(win)
  1367.     {
  1368.         if (domUtils)
  1369.         {
  1370.             // Normally these would not be required, but in order to update after the state is set
  1371.             // using the options menu we need to monitor these global events as well
  1372.             var doc = win.document;
  1373.             doc.addEventListener("mouseover", this.onHoverChange, false);
  1374.             doc.addEventListener("mousedown", this.onActiveChange, false);
  1375.         }
  1376.     },
  1377.     unwatchWindow: function(win)
  1378.     {
  1379.         var doc = win.document;
  1380.         doc.removeEventListener("mouseover", this.onHoverChange, false);
  1381.         doc.removeEventListener("mousedown", this.onActiveChange, false);
  1382.  
  1383.         if (isAncestor(this.stateChangeEl, doc))
  1384.         {
  1385.             this.removeStateChangeHandlers();
  1386.         }
  1387.     },
  1388.  
  1389.     supportsObject: function(object)
  1390.     {
  1391.         return object instanceof Element ? 1 : 0;
  1392.     },
  1393.  
  1394.     updateView: function(element)
  1395.     {
  1396.         this.updateCascadeView(element);
  1397.         if (domUtils)
  1398.         {
  1399.             this.contentState = safeGetContentState(element);
  1400.             this.addStateChangeHandlers(element);
  1401.         }
  1402.     },
  1403.  
  1404.     updateSelection: function(element)
  1405.     {
  1406.         if ( !(element instanceof Element) ) // html supports SourceLink
  1407.             return;
  1408.  
  1409.         if (sothinkInstalled)
  1410.         {
  1411.             FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode);
  1412.             return;
  1413.         }
  1414.  
  1415.         if (!domUtils)
  1416.         {
  1417.             FirebugReps.Warning.tag.replace({object: "DOMInspectorWarning"}, this.panelNode);
  1418.             return;
  1419.         }
  1420.  
  1421.         if (!element)
  1422.             return;
  1423.  
  1424.         this.updateView(element);
  1425.     },
  1426.  
  1427.     updateOption: function(name, value)
  1428.     {
  1429.         if (name == "showUserAgentCSS" || name == "expandShorthandProps")
  1430.             this.refresh();
  1431.     },
  1432.  
  1433.     getOptionsMenuItems: function()
  1434.     {
  1435.         var ret = [
  1436.             {label: "Show User Agent CSS", type: "checkbox", checked: Firebug.showUserAgentCSS,
  1437.                     command: bindFixed(Firebug.togglePref, Firebug, "showUserAgentCSS") },
  1438.             {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps,
  1439.                     command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") }
  1440.         ];
  1441.         if (domUtils && this.selection)
  1442.         {
  1443.             var state = safeGetContentState(this.selection);
  1444.  
  1445.             ret.push("-");
  1446.             ret.push({label: ":active", type: "checkbox", checked: state & STATE_ACTIVE,
  1447.               command: bindFixed(this.updateContentState, this, STATE_ACTIVE, state & STATE_ACTIVE)});
  1448.             ret.push({label: ":hover", type: "checkbox", checked: state & STATE_HOVER,
  1449.               command: bindFixed(this.updateContentState, this, STATE_HOVER, state & STATE_HOVER)});
  1450.         }
  1451.         return ret;
  1452.     },
  1453.  
  1454.     updateContentState: function(state, remove)
  1455.     {
  1456.         domUtils.setContentState(remove ? this.selection.ownerDocument.documentElement : this.selection, state);
  1457.         this.refresh();
  1458.     },
  1459.  
  1460.     addStateChangeHandlers: function(el)
  1461.     {
  1462.       this.removeStateChangeHandlers();
  1463.  
  1464.       el.addEventListener("focus", this.onStateChange, true);
  1465.       el.addEventListener("blur", this.onStateChange, true);
  1466.       el.addEventListener("mouseup", this.onStateChange, false);
  1467.       el.addEventListener("mousedown", this.onStateChange, false);
  1468.       el.addEventListener("mouseover", this.onStateChange, false);
  1469.       el.addEventListener("mouseout", this.onStateChange, false);
  1470.  
  1471.       this.stateChangeEl = el;
  1472.     },
  1473.  
  1474.     removeStateChangeHandlers: function()
  1475.     {
  1476.         var sel = this.stateChangeEl;
  1477.         if (sel)
  1478.         {
  1479.             sel.removeEventListener("focus", this.onStateChange, true);
  1480.             sel.removeEventListener("blur", this.onStateChange, true);
  1481.             sel.removeEventListener("mouseup", this.onStateChange, false);
  1482.             sel.removeEventListener("mousedown", this.onStateChange, false);
  1483.             sel.removeEventListener("mouseover", this.onStateChange, false);
  1484.             sel.removeEventListener("mouseout", this.onStateChange, false);
  1485.         }
  1486.     },
  1487.  
  1488.     contentStateCheck: function(state)
  1489.     {
  1490.       if (!state || this.contentState & state)
  1491.       {
  1492.           var timeoutRunner = bindFixed(function()
  1493.               {
  1494.                   var newState = safeGetContentState(this.selection);
  1495.                   if (newState != this.contentState)
  1496.                   {
  1497.                       this.context.invalidatePanels(this.name);
  1498.                   }
  1499.               }, this);
  1500.  
  1501.           // Delay exec until after the event has processed and the state has been updated
  1502.           setTimeout(timeoutRunner, 0);
  1503.       }
  1504.     }
  1505. });
  1506.  
  1507. function safeGetContentState(selection)
  1508. {
  1509.     try
  1510.     {
  1511.         return domUtils.getContentState(selection);
  1512.     }
  1513.     catch (e)
  1514.     {
  1515.     }
  1516. }
  1517.  
  1518. // ************************************************************************************************
  1519.  
  1520. function CSSComputedElementPanel() {}
  1521.  
  1522. CSSComputedElementPanel.prototype = extend(CSSElementPanel.prototype,
  1523. {
  1524.     template: domplate(
  1525.     {
  1526.         computedTag:
  1527.             DIV({"class": "a11yCSSView", role : "list", "aria-label" : $STR('aria.labels.computed styles')},
  1528.                 FOR("group", "$groups",
  1529.                     H1({class: "cssInheritHeader groupHeader focusRow", role : "listitem"},
  1530.                         SPAN({class: "cssInheritLabel"}, "$group.title")
  1531.                     ),
  1532.                     TABLE({width: "100%", role : 'group'},
  1533.                         TBODY({role : 'presentation'},
  1534.                             FOR("prop", "$group.props",
  1535.                                 TR({class : 'focusRow computedStyleRow', role : 'listitem'},
  1536.                                     TD({class: "stylePropName", role : 'presentation'}, "$prop.name"),
  1537.                                     TD({class: "stylePropValue", role : 'presentation'}, "$prop.value")
  1538.                                 )
  1539.                             )
  1540.                         )
  1541.                     )
  1542.                 )
  1543.             )
  1544.     }),
  1545.  
  1546.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1547.  
  1548.     updateComputedView: function(element)
  1549.     {
  1550.         var win = element.ownerDocument.defaultView;
  1551.         var style = win.getComputedStyle(element, "");
  1552.  
  1553.         var groups = [];
  1554.  
  1555.         for (var groupName in styleGroups)
  1556.         {
  1557.             var title = $STR("StyleGroup-" + groupName);
  1558.             var group = {title: title, props: []};
  1559.             groups.push(group);
  1560.  
  1561.             var props = styleGroups[groupName];
  1562.             for (var i = 0; i < props.length; ++i)
  1563.             {
  1564.                 var propName = props[i];
  1565.                 var propValue = stripUnits(rgbToHex(style.getPropertyValue(propName)));
  1566.                 if (propValue)
  1567.                     group.props.push({name: propName, value: propValue});
  1568.             }
  1569.         }
  1570.  
  1571.         var result = this.template.computedTag.replace({groups: groups}, this.panelNode);
  1572.         dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
  1573.     },
  1574.  
  1575.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1576.     // extends Panel
  1577.  
  1578.     name: "computed",
  1579.     parentPanel: "html",
  1580.     order: 1,
  1581.  
  1582.     updateView: function(element)
  1583.     {
  1584.         this.updateComputedView(element);
  1585.     },
  1586.  
  1587.     getOptionsMenuItems: function()
  1588.     {
  1589.         return [
  1590.             {label: "Refresh", command: bind(this.refresh, this) }
  1591.         ];
  1592.     }
  1593. });
  1594.  
  1595. // ************************************************************************************************
  1596. // CSSEditor
  1597.  
  1598. function CSSEditor(doc)
  1599. {
  1600.     this.initializeInline(doc);
  1601. }
  1602.  
  1603. CSSEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1604. {
  1605.     insertNewRow: function(target, insertWhere)
  1606.     {
  1607.         var rule = Firebug.getRepObject(target);
  1608.         var emptyProp = {name: "", value: "", important: ""};
  1609.  
  1610.         if (insertWhere == "before")
  1611.             return CSSPropTag.tag.insertBefore({prop: emptyProp, rule: rule}, target);
  1612.         else
  1613.             return CSSPropTag.tag.insertAfter({prop: emptyProp, rule: rule}, target);
  1614.     },
  1615.  
  1616.     saveEdit: function(target, value, previousValue)
  1617.     {
  1618.         target.innerHTML = escapeForCss(value);
  1619.  
  1620.         var row = getAncestorByClass(target, "cssProp");
  1621.         if (hasClass(row, "disabledStyle"))
  1622.             toggleClass(row, "disabledStyle");
  1623.  
  1624.         var rule = Firebug.getRepObject(target);
  1625.  
  1626.         if (hasClass(target, "cssPropName"))
  1627.         {
  1628.             if (value && previousValue != value)  // name of property has changed.
  1629.             {
  1630.                 var propValue = getChildByClass(row, "cssPropValue").textContent;
  1631.                 var parsedValue = parsePriority(propValue);
  1632.  
  1633.                 if (propValue && propValue != "undefined") {
  1634.                     if (previousValue)
  1635.                         Firebug.CSSModule.removeProperty(rule, previousValue);
  1636.                     Firebug.CSSModule.setProperty(rule, value, parsedValue.value, parsedValue.priority);
  1637.                 }
  1638.             }
  1639.             else if (!value) // name of the property has been deleted, so remove the property.
  1640.                 Firebug.CSSModule.removeProperty(rule, previousValue);
  1641.         }
  1642.         else if (getAncestorByClass(target, "cssPropValue"))
  1643.         {
  1644.             var propName = getChildByClass(row, "cssPropName").textContent;
  1645.             var propValue = getChildByClass(row, "cssPropValue").textContent;
  1646.  
  1647.             if (value && value != "null")
  1648.             {
  1649.                 var parsedValue = parsePriority(value);
  1650.                 Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority);
  1651.             }
  1652.             else if (previousValue && previousValue != "null")
  1653.                 Firebug.CSSModule.removeProperty(rule, propName);
  1654.         }
  1655.  
  1656.         this.panel.markChange(this.panel.name == "stylesheet");
  1657.     },
  1658.  
  1659.     advanceToNext: function(target, charCode)
  1660.     {
  1661.         if (charCode == 58 /*":"*/ && hasClass(target, "cssPropName"))
  1662.             return true;
  1663.     },
  1664.  
  1665.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1666.  
  1667.     getAutoCompleteRange: function(value, offset)
  1668.     {
  1669.         if (hasClass(this.target, "cssPropName"))
  1670.             return {start: 0, end: value.length-1};
  1671.         else
  1672.             return parseCSSValue(value, offset);
  1673.     },
  1674.  
  1675.     getAutoCompleteList: function(preExpr, expr, postExpr)
  1676.     {
  1677.         if (hasClass(this.target, "cssPropName"))
  1678.         {
  1679.             return getCSSPropertyNames();
  1680.         }
  1681.         else
  1682.         {
  1683.             var row = getAncestorByClass(this.target, "cssProp");
  1684.             var propName = getChildByClass(row, "cssPropName").textContent;
  1685.             return getCSSKeywordsByProperty(propName);
  1686.         }
  1687.     }
  1688. });
  1689.  
  1690. //************************************************************************************************
  1691. //CSSRuleEditor
  1692.  
  1693. function CSSRuleEditor(doc)
  1694. {
  1695.     this.initializeInline(doc);
  1696.     this.completeAsYouType = false;
  1697. }
  1698. CSSRuleEditor.uniquifier = 0;
  1699. CSSRuleEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1700. {
  1701.     insertNewRow: function(target, insertWhere)
  1702.     {
  1703.          var emptyRule = {
  1704.                  selector: "",
  1705.                  id: "",
  1706.                  props: [],
  1707.                  isSelectorEditable: true
  1708.          };
  1709.  
  1710.          if (insertWhere == "before")
  1711.              return CSSStyleRuleTag.tag.insertBefore({rule: emptyRule}, target);
  1712.          else
  1713.              return CSSStyleRuleTag.tag.insertAfter({rule: emptyRule}, target);
  1714.     },
  1715.  
  1716.     saveEdit: function(target, value, previousValue)
  1717.     {
  1718.         target.innerHTML = escapeForCss(value);
  1719.  
  1720.         if (value === previousValue)     return;
  1721.  
  1722.         var row = getAncestorByClass(target, "cssRule");
  1723.         var styleSheet = this.panel.location;
  1724.         styleSheet = styleSheet.editStyleSheet ? styleSheet.editStyleSheet.sheet : styleSheet;
  1725.  
  1726.         var cssRules = styleSheet.cssRules;
  1727.         var rule = Firebug.getRepObject(target), oldRule = rule;
  1728.         var ruleIndex = cssRules.length;
  1729.         if (rule || Firebug.getRepObject(row.nextSibling))
  1730.         {
  1731.             var searchRule = rule || Firebug.getRepObject(row.nextSibling);
  1732.             for (ruleIndex=0; ruleIndex<cssRules.length && searchRule!=cssRules[ruleIndex]; ruleIndex++) {}
  1733.         }
  1734.  
  1735.         // Delete in all cases except for new add
  1736.         // We want to do this before the insert to ease change tracking
  1737.         if (oldRule)
  1738.         {
  1739.             Firebug.CSSModule.deleteRule(styleSheet, ruleIndex);
  1740.         }
  1741.  
  1742.         // Firefox does not follow the spec for the update selector text case.
  1743.         // When attempting to update the value, firefox will silently fail.
  1744.         // See https://bugzilla.mozilla.org/show_bug.cgi?id=37468 for the quite
  1745.         // old discussion of this bug.
  1746.         // As a result we need to recreate the style every time the selector
  1747.         // changes.
  1748.         if (value)
  1749.         {
  1750.             var cssText = [ value, "{", ];
  1751.             var props = row.getElementsByClassName("cssProp");
  1752.             for (var i = 0; i < props.length; i++) {
  1753.                 var propEl = props[i];
  1754.                 if (!hasClass(propEl, "disabledStyle")) {
  1755.                     cssText.push(getChildByClass(propEl, "cssPropName").textContent);
  1756.                     cssText.push(":");
  1757.                     cssText.push(getChildByClass(propEl, "cssPropValue").textContent);
  1758.                     cssText.push(";");
  1759.                 }
  1760.             }
  1761.             cssText.push("}");
  1762.             cssText = cssText.join("");
  1763.  
  1764.             try
  1765.             {
  1766.                 var insertLoc = Firebug.CSSModule.insertRule(styleSheet, cssText, ruleIndex);
  1767.                 rule = cssRules[insertLoc];
  1768.                 ruleIndex++;
  1769.             }
  1770.             catch (err)
  1771.             {
  1772.                 target.innerHTML = escapeForCss(previousValue);
  1773.                 row.repObject = undefined;
  1774.                 return;
  1775.             }
  1776.         } else {
  1777.             rule = undefined;
  1778.         }
  1779.  
  1780.         // Update the rep object
  1781.         row.repObject = rule;
  1782.         if (!oldRule)
  1783.         {
  1784.             // Who knows what the domutils will return for rule line
  1785.             // for a recently created rule. To be safe we just generate
  1786.             // a unique value as this is only used as an internal key.
  1787.             var ruleId = "new/"+value+"/"+(++CSSRuleEditor.uniquifier);
  1788.             row.setAttribute("ruleId", ruleId);
  1789.         }
  1790.  
  1791.         this.panel.markChange(this.panel.name == "stylesheet");
  1792.     }
  1793. });
  1794.  
  1795. // ************************************************************************************************
  1796. // StyleSheetEditor
  1797.  
  1798. function StyleSheetEditor(doc)
  1799. {
  1800.     this.box = this.tag.replace({}, doc, this);
  1801.     this.input = this.box.firstChild;
  1802. }
  1803.  
  1804. StyleSheetEditor.prototype = domplate(Firebug.BaseEditor,
  1805. {
  1806.     multiLine: true,
  1807.  
  1808.     tag: DIV(
  1809.         TEXTAREA({class: "styleSheetEditor fullPanelEditor", oninput: "$onInput"})
  1810.     ),
  1811.  
  1812.     getValue: function()
  1813.     {
  1814.         return this.input.value;
  1815.     },
  1816.  
  1817.     setValue: function(value)
  1818.     {
  1819.         return this.input.value = value;
  1820.     },
  1821.  
  1822.     show: function(target, panel, value, textSize, targetSize)
  1823.     {
  1824.         this.target = target;
  1825.         this.panel = panel;
  1826.  
  1827.         this.panel.panelNode.appendChild(this.box);
  1828.  
  1829.         this.input.value = value;
  1830.         this.input.focus();
  1831.  
  1832.         var command = Firebug.chrome.$("cmd_toggleCSSEditing");
  1833.         command.setAttribute("checked", true);
  1834.     },
  1835.  
  1836.     hide: function()
  1837.     {
  1838.         var command = Firebug.chrome.$("cmd_toggleCSSEditing");
  1839.         command.setAttribute("checked", false);
  1840.  
  1841.         if (this.box.parentNode == this.panel.panelNode)
  1842.             this.panel.panelNode.removeChild(this.box);
  1843.  
  1844.         delete this.target;
  1845.         delete this.panel;
  1846.         delete this.styleSheet;
  1847.     },
  1848.  
  1849.     saveEdit: function(target, value, previousValue)
  1850.     {
  1851.         Firebug.CSSModule.freeEdit(this.styleSheet, value);
  1852.     },
  1853.  
  1854.     endEditing: function()
  1855.     {
  1856.         this.panel.refresh();
  1857.         return true;
  1858.     },
  1859.  
  1860.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1861.  
  1862.     onInput: function()
  1863.     {
  1864.         Firebug.Editor.update();
  1865.     },
  1866.  
  1867.     scrollToLine: function(line, offset)
  1868.     {
  1869.         this.startMeasuring(this.input);
  1870.         var lineHeight = this.measureText().height;
  1871.         this.stopMeasuring();
  1872.  
  1873.         this.input.scrollTop = (line * lineHeight) + offset;
  1874.     }
  1875. });
  1876.  
  1877. // ************************************************************************************************
  1878. // Local Helpers
  1879.  
  1880. function rgbToHex(value)
  1881. {
  1882.     return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, function(_, r, g, b) {
  1883.     return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase();
  1884.     });
  1885. }
  1886.  
  1887. function stripUnits(value)
  1888. {
  1889.     // remove units from '0px', '0em' etc. leave non-zero units in-tact.
  1890.     return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi, function(_, skip, remove, whitespace) {
  1891.     return skip || ('0' + whitespace);
  1892.     });
  1893. }
  1894.  
  1895. function parsePriority(value)
  1896. {
  1897.     var rePriority = /(.*?)\s*(!important)?$/;
  1898.     var m = rePriority.exec(value);
  1899.     var propValue = m ? m[1] : "";
  1900.     var priority = m && m[2] ? "important" : "";
  1901.     return {value: propValue, priority: priority};
  1902. }
  1903.  
  1904. function parseURLValue(value)
  1905. {
  1906.     var m = reURL.exec(value);
  1907.     return m ? m[1] : "";
  1908. }
  1909.  
  1910. function parseRepeatValue(value)
  1911. {
  1912.     var m = reRepeat.exec(value);
  1913.     return m ? m[0] : "";
  1914. }
  1915.  
  1916. function parseCSSValue(value, offset)
  1917. {
  1918.     var start = 0;
  1919.     var m;
  1920.     while (1)
  1921.     {
  1922.         m = reSplitCSS.exec(value);
  1923.         if (m && m.index+m[0].length < offset)
  1924.         {
  1925.             value = value.substr(m.index+m[0].length);
  1926.             start += m.index+m[0].length;
  1927.             offset -= m.index+m[0].length;
  1928.         }
  1929.         else
  1930.             break;
  1931.     }
  1932.  
  1933.     if (m)
  1934.     {
  1935.         var type;
  1936.         if (m[1])
  1937.             type = "url";
  1938.         else if (m[2] || m[3])
  1939.             type = "rgb";
  1940.         else if (m[4])
  1941.             type = "int";
  1942.  
  1943.         return {value: m[0], start: start+m.index, end: start+m.index+(m[0].length-1), type: type};
  1944.     }
  1945. }
  1946.  
  1947. function findPropByName(props, name)
  1948. {
  1949.     for (var i = 0; i < props.length; ++i)
  1950.     {
  1951.         if (props[i].name == name)
  1952.             return i;
  1953.     }
  1954. }
  1955.  
  1956. function sortProperties(props)
  1957. {
  1958.     props.sort(function(a, b)
  1959.     {
  1960.         return a.name > b.name ? 1 : -1;
  1961.     });
  1962. }
  1963.  
  1964. function getTopmostRuleLine(panelNode)
  1965. {
  1966.     for (var child = panelNode.firstChild; child; child = child.nextSibling)
  1967.     {
  1968.         if (child.offsetTop+child.offsetHeight > panelNode.scrollTop)
  1969.         {
  1970.             var rule = child.repObject;
  1971.             if (rule)
  1972.                 return {
  1973.                     line: domUtils.getRuleLine(rule),
  1974.                     offset: panelNode.scrollTop-child.offsetTop
  1975.                 };
  1976.         }
  1977.     }
  1978.     return 0;
  1979. }
  1980.  
  1981. function getStyleSheetCSS(sheet, context)
  1982. {
  1983.     if (sheet.ownerNode instanceof HTMLStyleElement)
  1984.         return sheet.ownerNode.innerHTML;
  1985.     else
  1986.         return context.sourceCache.load(sheet.href).join("");
  1987. }
  1988.  
  1989. function getStyleSheetOwnerNode(sheet) {
  1990.     for (; sheet && !sheet.ownerNode; sheet = sheet.parentStyleSheet);
  1991.  
  1992.     return sheet.ownerNode;
  1993. }
  1994.  
  1995. function scrollSelectionIntoView(panel)
  1996. {
  1997.     var selCon = getSelectionController(panel);
  1998.     selCon.scrollSelectionIntoView(
  1999.             nsISelectionController.SELECTION_NORMAL,
  2000.             nsISelectionController.SELECTION_FOCUS_REGION, true);
  2001. }
  2002.  
  2003. function getSelectionController(panel)
  2004. {
  2005.     var browser = Firebug.chrome.getPanelBrowser(panel);
  2006.     return browser.docShell.QueryInterface(nsIInterfaceRequestor)
  2007.         .getInterface(nsISelectionDisplay)
  2008.         .QueryInterface(nsISelectionController);
  2009. }
  2010.  
  2011. // ************************************************************************************************
  2012.  
  2013. Firebug.registerModule(Firebug.CSSModule);
  2014. Firebug.registerPanel(Firebug.CSSStyleSheetPanel);
  2015. Firebug.registerPanel(CSSElementPanel);
  2016. Firebug.registerPanel(CSSComputedElementPanel);
  2017.  
  2018. // ************************************************************************************************
  2019.  
  2020. }});
  2021.